package edu.mercer.sse661.ao;

import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Throwables;
import com.google.common.collect.Queues;
import com.google.common.hash.HashCode;

import static com.google.common.base.Preconditions.*;

/**
 * Active Object Pattern Demonstration: Proxy implementation.<p>
 * 
 * Asynchronous download of URL contents to StringBuilder.<p>
 * 
 * Use with websites. Example parameter: "http://google.com"<p>
 * 
 * Summary:<p>
 * 
 * Asynchronous download. Scheduler monitors and starts download tasks 
 * 	individually in its own single thread.<p> 
 * 
 * Detail:<p>
 * 
 * BlockingQueue's provide guard against simultaneous task execution or 
 * 	Future retrieval. 'Method Request' accomplished using BlockingQueue. 
 * 	Eliminates separate need for a 'Guard' within each task, but makes 
 * 	'Scheduler' less reusable as expects BlockingQueue; i.e., Inheritance 
 * 	Anomaly.<p>
 * 
 * Dedicated threadpool used by 'Scheduler' to execute each download task. 
 * 	Execution threads of 'Servant' are separate from 'Scheduler' thread 
 * 	awaiting new download tasks. Scheduler-Servant integrated as 
 * 	WebRetrievalScheduler.<p> 
 * 
 * AutoCloseable used to shutdown single thread running scheduler. Its 
 * 	execution threadpool is shutdown in turn by an AutoCloseable 
 * 	implementation.<p>
 *
 * @see WebRetrievalScheduler
 * @see ActiveObject
 * @see DownloadURL
 * @see AutoCloseable
 */

public class WebsiteRetrieval implements 

	ActiveObject<DownloadURL,Future<StringBuilder>>,

	AutoCloseable
{
	/** 
	 * Maximum concurrent downloads. When number reached, waits until one 
	 * completes before accepting another download task.<p> 
	 */
	
	static Integer _maxTasks = 10;

	/**
	 * Maximum timeout in seconds. Waiting for download to complete, or 
	 * 	BlockingQueue's to become responsive.<p>
	 */
	
	static Integer _maxWait = 35;

	/**
	 * Download tasks as BlockingQueue of fixed size.<p> 
	 * 
	 * Modified across threads by Scheduler-Servant.<p>
	 */
	
	private final BlockingQueue<DownloadURL> _downloadTasks = Queues
			.newLinkedBlockingQueue( _maxTasks );

	/**
	 * Asynchronous Futures from started download tasks as BlockingQueue of 
	 * fixed size.<p> 
	 * 
	 * Modified across threads by Scheduler-Servant.<p>
	 */
	
	private final BlockingQueue<Future<StringBuilder>> _results = Queues
			.newLinkedBlockingQueue( _maxTasks );
	
	/**
	 * Separate dedicate single thread for Scheduler-Servant.<p>
	 * 
	 * Used by 'Scheduler' to monitor continuously for new tasks. 'Scheduler' 
	 * 	in turn acts as 'Servant', executing tasks in separate threads.<p>
	 */
	
	private final ExecutorService _exeSingleSvc = Executors
			.newSingleThreadExecutor();
	
	
	private HashCode _hashCode = HashCode.fromInt(this.hashCode());
	

	/**
	 * Constructor.<p>
	 * 
	 * Initiates 'Scheduler' processing loop monitoring for new download 
	 * 	tasks.<p>
	 */

	WebsiteRetrieval()
	{
		super();
		
		startScheduler();
		
	}//constructor


	/**
	 * Constructor.<p>
	 * 
	 * Override default maximum tasks or timeout.<p>
	 * 
	 * Initiates 'Scheduler' processing loop monitoring for new download 
	 * 	tasks.<p>
	 * 
	 * @param maxTasks - maximum tasks
	 * 
	 * @param maxWait - maxWait timeout
	 * 
	 * @see #_maxTasks
	 * 
	 * @see #_maxWait
	 */
	
	WebsiteRetrieval( final Integer maxTasks, final Integer maxWait )
	{
		super();
		
		_maxTasks = maxTasks;
		
		_maxWait = maxWait;
		
		startScheduler();
		
	}//constructor
	
	
	/**
	 * Starts 'Scheduler' processing loop in its own thread. 'Scheduler'
	 * 	starts 'Servant' in effect by executing each task in a new thread.<p>
	 */
	
	void startScheduler()
	{		
		_exeSingleSvc.execute( new WebRetrievalScheduler(this, _hashCode) );

	}//method
	
	
	/**
	 * Get Tasks: DownloadURL BlockingQueue.<p>
	 * 
	 * @return BlockingQueue&#60;DownloadURL&#62;
	 */

	protected BlockingQueue<DownloadURL> getTasks
		( HashCode proxyHash )
	{
		checkArgument( proxyHash.equals(_hashCode), "only permit "
				+ "Scheduler-Servant to call this method" );
		
		return _downloadTasks;
		
	}//method

	
	/**
	 * Get Results Futures: StringBuilder BlockingQueue.<p>
	 * 
	 * @return BlockingQueue&#60;Future&#60;StringBuilder&#62;&#62;
	 */

	protected BlockingQueue<Future<StringBuilder>> getResults
		( HashCode proxyHash )
	{	
		checkArgument( proxyHash.equals(_hashCode), "only permit "
				+ "Scheduler-Servant to call this method" );
		
		return _results;
		
	}//method


	/**
	 * Get a Future for awaiting download completion and results.<p> 
	 * 
	 * Successful result is StringBuilder containing contents of download.<p>
	 */
	
	public Future<StringBuilder> get()
	{
		try
		{
			return _results.poll( _maxWait, TimeUnit.SECONDS );
		
		}
		catch( final InterruptedException err )
		{
			throw new RuntimeException( "timeout waiting on retrieval of "
					+ "download task instance completed results: " 
					+ " " 
					+ Throwables.getStackTraceAsString(err) );			
			
		}//try
		
	}//method


	/**
	 * Put a new download task.<p>
	 * 
	 * Scheduled for execution. 'Scheduler' thread monitors change in 
	 * 	BlockingQueue and as 'Servant', executes in new thread.<p>
	 * 
	 * @param task - DownloadURL Runnable
	 * 
	 * @see DownloadURL
	 */
	
	public void put( final DownloadURL task )
	{
		try
		{		
			_downloadTasks.put( task );
		
		}
		catch( final InterruptedException err )
		{
			throw new RuntimeException( "timeout waiting space available in "
					+ "download task queue: " 
					+ " " 
					+ Throwables.getStackTraceAsString(err) );			
			
		}//try

	}//method

	
	/**
	 * Shutdown single thread use by 'Scheduler' processing loop to monitor 
	 * 	for new download tasks.
	 */

	public void close() throws IOException
	{
		_exeSingleSvc.shutdown();
	

	}//method
	
		
}//class
